/*-------------------------------------------------------------------------
 *
 * crypt.c
 *	  用于处理存储在pg_authid.rolpassword中的加密密码的函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/libpq/crypt.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#include "catalog/pg_authid.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/crypt.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"

#ifdef FDDGM
#include "common/fdb_sm3.h"
#endif


/*
 * 为用户获取存储的密码，用于身份验证。
 *
 * 出现错误时，返回 NULL，并将描述原因的 palloc'd 字符串存储在 *logdetail 中，供后端日志使用。错误原因 *不应* 发送给客户端，以避免泄露用户信息！
 */
char * get_role_password(const char *fc_role, const char **fc_logdetail)
{
	TimestampTz fc_vuntil = 0;
	HeapTuple	fc_roleTup;
	Datum		fc_datum;
	bool		fc_isnull;
	char	   *fc_shadow_pass;

	/* 从 pg_authid 获取角色信息 */
	fc_roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(fc_role));
	if (!HeapTupleIsValid(fc_roleTup))
	{
		*fc_logdetail = psprintf(_("Role \"%s\" does not exist."),
							  fc_role);
		return NULL;			/* 用户不存在 */
	}

	fc_datum = SysCacheGetAttr(AUTHNAME, fc_roleTup,
							Anum_pg_authid_rolpassword, &fc_isnull);
	if (fc_isnull)
	{
		ReleaseSysCache(fc_roleTup);
		*fc_logdetail = psprintf(_("User \"%s\" has no password assigned."),
							  fc_role);
		return NULL;			/* 用户没有密码 */
	}
	fc_shadow_pass = TextDatumGetCString(fc_datum);

	fc_datum = SysCacheGetAttr(AUTHNAME, fc_roleTup,
							Anum_pg_authid_rolvaliduntil, &fc_isnull);
	if (!fc_isnull)
		fc_vuntil = DatumGetTimestampTz(fc_datum);

	ReleaseSysCache(fc_roleTup);

	/*
	 * 密码正确，但检查一下我们是否已超过 rolvaliduntil
	 */
	if (!fc_isnull && fc_vuntil < GetCurrentTimestamp())
	{
		*fc_logdetail = psprintf(_("User \"%s\" has an expired password."),
							  fc_role);
		return NULL;
	}

	return fc_shadow_pass;
}

/*
 * 'shadow_pass' 是什么类型的密码？
 */
PasswordType
get_password_type(const char *fc_shadow_pass)
{
	char	   *fc_encoded_salt;
	int			fc_iterations;
	uint8		fc_stored_key[SCRAM_KEY_LEN];
	uint8		fc_server_key[SCRAM_KEY_LEN];

	if (strncmp(fc_shadow_pass, "md5", 3) == 0 &&
		strlen(fc_shadow_pass) == MD5_PASSWD_LEN &&
		strspn(fc_shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
		return PASSWORD_TYPE_MD5;
#ifdef FDDGM
	if (strncmp(fc_shadow_pass, "SM3", 3) == 0 &&
		strlen(fc_shadow_pass) == SM3_PASSWD_LEN &&
		strspn(fc_shadow_pass + 3, SM3_PASSWD_CHARSET) == SM3_PASSWD_LEN - 3)
		return PASSWORD_TYPE_SM3;
#endif
	if (parse_scram_secret(fc_shadow_pass, &fc_iterations, &fc_encoded_salt,
						   fc_stored_key, fc_server_key))
		return PASSWORD_TYPE_SCRAM_SHA_256;
	return PASSWORD_TYPE_PLAINTEXT;
}

/*
 * 给定用户提供的密码，将其转换为 'target_type' 类型的密钥。
 *
 * 如果密码已经是加密形式，我们无法反转哈希，因此无论请求的类型是什么，都会按原样存储。
 */
char * encrypt_password(PasswordType fc_target_type, const char *fc_role,
				 const char *fc_password)
{
	PasswordType fc_guessed_type = get_password_type(fc_password);
	char	   *fc_encrypted_password;
	const char *fc_errstr = NULL;

	if (fc_guessed_type != PASSWORD_TYPE_PLAINTEXT)
	{
		/*
		 * 无法将已加密的密码从一种格式转换为另一种格式，因此按原样返回。
		 */
		return pstrdup(fc_password);
	}

	switch (fc_target_type)
	{
		case PASSWORD_TYPE_MD5:
			fc_encrypted_password = palloc(MD5_PASSWD_LEN + 1);

			if (!pg_md5_encrypt(fc_password, fc_role, strlen(fc_role),
								fc_encrypted_password, &fc_errstr))
				elog(ERROR, "password encryption failed: %s", fc_errstr);
			return fc_encrypted_password;

#ifdef FDDGM
		case PASSWORD_TYPE_SM3:
		{
			fc_encrypted_password = palloc(SM3_PASSWD_LEN + 1);
			if (!fdb_sm3_encrypt(fc_password, fc_role, strlen(fc_role),
								fc_encrypted_password, &fc_errstr))
				elog(ERROR, "password encryption failed: %s", fc_errstr);
			return fc_encrypted_password;
		}
#endif

		case PASSWORD_TYPE_SCRAM_SHA_256:
			return pg_be_scram_build_secret(fc_password);

		case PASSWORD_TYPE_PLAINTEXT:
			elog(ERROR, "cannot encrypt password with 'plaintext'");
	}

	/*
	 * 这不应该发生，因为上述 switch 语句应该处理每种源密码类型和目标密码类型的组合。
	 */
	elog(ERROR, "cannot encrypt password to requested type");
	return NULL;				/* 保持编译器安静 */
}


#ifdef FDDGM
int sm3_crypt_verify(const char *fc_role, const char *fc_shadow_pass,
				 const char *fc_client_pass,
				 const char *fc_salt, int fc_salt_len,
				 const char **fc_logdetail)
{
	int			fc_retval;
	char		fc_crypt_pwd[SM3_PASSWD_LEN + 1];
	const char *fc_errstr = NULL;

	if (get_password_type(fc_shadow_pass) != PASSWORD_TYPE_SM3)
	{
		/* 不兼容的密码哈希格式。 */
		*fc_logdetail = psprintf(_("User \"%s\" has a password that cannot be used with SM3 authentication."),
							  fc_role);
		return STATUS_ERROR;
	}

	/*
	 * 计算 MD5 挑战的正确答案。
	 */
	/* 已加密的存储密码，仅进行盐处理 */
	if (!fdb_sm3_encrypt(fc_shadow_pass + SM3_LENGTH,
						fc_salt, fc_salt_len,
						fc_crypt_pwd, &fc_errstr))
	{
		*fc_logdetail = fc_errstr;
		return STATUS_ERROR;
	}

	if (strcmp(fc_client_pass, fc_crypt_pwd) == 0)
		fc_retval = STATUS_OK;
	else
	{
		*fc_logdetail = psprintf(_("Password does not match for user \"%s\"."),
							  fc_role);
		fc_retval = STATUS_ERROR;
	}

	return fc_retval;
}
#endif

/*
 * 检查 MD5 身份验证响应，并返回 STATUS_OK 或 STATUS_ERROR。
 *
 * 'shadow_pass' 是用户的正确密码或密码哈希，存储在 pg_authid.rolpassword 中。
 * 'client_pass' 是远程用户对 MD5 挑战的响应。
 * 'md5_salt' 是 MD5 身份验证挑战中使用的盐。
 *
 * 在错误情况下，在 *logdetail 中保存一个字符串，该字符串将发送到后端日志（但不会发送给客户端）。
 */
int md5_crypt_verify(const char *fc_role, const char *fc_shadow_pass,
				 const char *fc_client_pass,
				 const char *fc_md5_salt, int fc_md5_salt_len,
				 const char **fc_logdetail)
{
	int			fc_retval;
	char		fc_crypt_pwd[MD5_PASSWD_LEN + 1];
	const char *fc_errstr = NULL;

	Assert(fc_md5_salt_len > 0);

	if (get_password_type(fc_shadow_pass) != PASSWORD_TYPE_MD5)
	{
		/* 不兼容的密码哈希格式。 */
		*fc_logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
							  fc_role);
		return STATUS_ERROR;
	}

	/*
	 * 计算 MD5 挑战的正确答案。
	 */
	/* 已加密的存储密码，仅进行盐处理 */
	if (!pg_md5_encrypt(fc_shadow_pass + strlen("md5"),
						fc_md5_salt, fc_md5_salt_len,
						fc_crypt_pwd, &fc_errstr))
	{
		*fc_logdetail = fc_errstr;
		return STATUS_ERROR;
	}

	if (strcmp(fc_client_pass, fc_crypt_pwd) == 0)
		fc_retval = STATUS_OK;
	else
	{
		*fc_logdetail = psprintf(_("Password does not match for user \"%s\"."),
							  fc_role);
		fc_retval = STATUS_ERROR;
	}

	return fc_retval;
}

/*
 * 检查给定用户的给定密码，并返回 STATUS_OK 或 STATUS_ERROR。
 *
 * 'shadow_pass' 是用户的正确密码哈希，存储在 pg_authid.rolpassword 中。
 * 'client_pass' 是远程用户提供的密码。
 *
 * 在错误情况下，将字符串存储在 *logdetail 中，该字符串将发送到后端日志（但不会发送给客户端）。
 */
int plain_crypt_verify(const char *fc_role, const char *fc_shadow_pass,
				   const char *fc_client_pass,
				   const char **fc_logdetail)
{
#ifdef FDDGM
	char		fc_sm3_client_pass[SM3_PASSWD_LEN + 1];
#endif
	char		fc_crypt_client_pass[MD5_PASSWD_LEN + 1];
	const char *fc_errstr = NULL;

	/*
	 * 客户端以明文发送密码。如果我们存储了 MD5 哈希，则对客户端发送的密码进行哈希，并比较哈希值。否则，直接比较明文密码。
	 */
	switch (get_password_type(fc_shadow_pass))
	{
		case PASSWORD_TYPE_SCRAM_SHA_256:
			if (scram_verify_plain_password(fc_role,
											fc_client_pass,
											fc_shadow_pass))
			{
				return STATUS_OK;
			}
			else
			{
				*fc_logdetail = psprintf(_("Password does not match for user \"%s\"."),
									  fc_role);
				return STATUS_ERROR;
			}
			break;

		case PASSWORD_TYPE_MD5:
			if (!pg_md5_encrypt(fc_client_pass,
								fc_role,
								strlen(fc_role),
								fc_crypt_client_pass,
								&fc_errstr))
			{
				*fc_logdetail = fc_errstr;
				return STATUS_ERROR;
			}
			if (strcmp(fc_crypt_client_pass, fc_shadow_pass) == 0)
				return STATUS_OK;
			else
			{
				*fc_logdetail = psprintf(_("Password does not match for user \"%s\"."),
									  fc_role);
				return STATUS_ERROR;
			}
			break;

#ifdef FDDGM
		case PASSWORD_TYPE_SM3:
			if(!fdb_sm3_encrypt(fc_client_pass,
								fc_role,
								strlen(fc_role),
								fc_sm3_client_pass,
								&fc_errstr))
			{
				*fc_logdetail = fc_errstr;
				return STATUS_ERROR;
			}
			if(strcmp(fc_sm3_client_pass, fc_shadow_pass) == 0)
			{
				return STATUS_OK;
			}
			else
			{
				*fc_logdetail = psprintf(_("Password does not match for user \"%s\"."),
									  fc_role);
				return STATUS_ERROR;
			}
			break;
#endif

		case PASSWORD_TYPE_PLAINTEXT:

			/*
			 * 我们从不以明文存储密码，所以这不应该发生。
			 */
			break;
	}

	/*
	 * 这不应该发生。 明文 "密码" 身份验证可以与任何类型的存储密码哈希一起使用。
	 */
	*fc_logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
						  fc_role);
	return STATUS_ERROR;
}
